Skip to content

agent: @U0AJM7X8FBR Admin + Docs + API - we want a new admin page to view analy#20

Merged
recoup-coding-agent merged 4 commits intomainfrom
agent/-u0ajm7x8fbr-admin---docs---ap-1774265132075
Mar 23, 2026
Merged

agent: @U0AJM7X8FBR Admin + Docs + API - we want a new admin page to view analy#20
recoup-coding-agent merged 4 commits intomainfrom
agent/-u0ajm7x8fbr-admin---docs---ap-1774265132075

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented Mar 23, 2026

Automated PR from coding agent.

Summary by CodeRabbit

  • New Features
    • Added "Coding Agent Tags" section to the admin dashboard for comprehensive Slack tagging analytics.
    • Filter tag data by time period: all, daily, weekly, or monthly.
    • View tagging trends in an interactive line chart and browse detailed records in a sortable table.
    • Tag records display user name, prompt text, channel, and timestamp information.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin Ready Ready Preview Mar 23, 2026 5:17pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e591241b-6353-4b50-8baf-9674db047481

📥 Commits

Reviewing files that changed from the base of the PR and between b59e000 and 75c80cd.

📒 Files selected for processing (2)
  • components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx
  • components/PrivyLogins/PrivyLoginsPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx

📝 Walkthrough

Walkthrough

A new admin page is added to display Slack tagging analytics for a Coding Agent feature, including period-based filtering, a line chart visualization, and a sortable table of tag records with user and channel information.

Changes

Cohort / File(s) Summary
Slack Tags Admin UI Components
components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx, components/CodingAgentSlackTags/SlackTagsTable.tsx, components/CodingAgentSlackTags/SlackTagsColumns.tsx
New page and table components for displaying Slack tag analytics with period filtering, results count, loading/error states, and a TanStack-based table with sortable timestamp column and user avatars.
Data Fetching Layer
hooks/useSlackTags.ts, lib/recoup/fetchSlackTags.ts
New hook and API client for fetching Slack tag data from the backend, including authentication via Privy access token and error handling with status code interpretation.
Data Transformation & Utilities
lib/coding-agent/getTagsByDate.ts, components/Admin/AdminLineChart.tsx
Helper function to aggregate tags by date and a reusable line chart component for rendering time-series analytics with configurable labels.
Type Definitions
types/coding-agent.ts, types/admin.ts, types/privy.ts
New types for Slack tag entities and responses; extracted shared AdminPeriod type to consolidate period definitions across features, with PrivyLoginsPeriod now aliasing AdminPeriod.
Generic Admin Components
components/Admin/PeriodSelector.tsx
Renamed from PrivyPeriodSelector to PeriodSelector and updated to use generic AdminPeriod type instead of Privy-specific type, making it reusable across admin features.
Navigation & Routing
components/Home/AdminDashboard.tsx, app/coding/page.tsx
Added navigation link to new Coding Agent Tags page in admin dashboard and created the route with page metadata.
Updated Existing Component
components/PrivyLogins/PrivyLoginsPage.tsx
Refactored to use the newly generalized PeriodSelector and AdminLineChart components instead of Privy-specific variants, utilizing the new getLastSeenByDate utility pattern.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser
    participant CodingAgentSlackTagsPage as CodingAgentSlackTagsPage
    participant useSlackTags as useSlackTags Hook
    participant Privy as Privy (Auth)
    participant API as Backend API
    participant TanStack as TanStack React Table

    Browser->>CodingAgentSlackTagsPage: Load page with period="all"
    CodingAgentSlackTagsPage->>useSlackTags: useSlackTags("all")
    useSlackTags->>Privy: getAccessToken()
    Privy-->>useSlackTags: accessToken
    useSlackTags->>API: fetchSlackTags(token, "all")
    API-->>useSlackTags: SlackTagsResponse { total, tags[] }
    useSlackTags-->>CodingAgentSlackTagsPage: { data, loading, error }
    
    alt Loading
        CodingAgentSlackTagsPage->>Browser: Render ChartSkeleton + TableSkeleton
    else Error
        CodingAgentSlackTagsPage->>Browser: Render Error Alert
    else Success
        CodingAgentSlackTagsPage->>CodingAgentSlackTagsPage: getTagsByDate(tags) → chart data
        CodingAgentSlackTagsPage->>Browser: Render AdminLineChart
        CodingAgentSlackTagsPage->>TanStack: useReactTable(tags, slackTagsColumns)
        TanStack-->>CodingAgentSlackTagsPage: sorted rows
        CodingAgentSlackTagsPage->>Browser: Render SlackTagsTable
    end
    
    Browser->>CodingAgentSlackTagsPage: onPeriodChange("weekly")
    CodingAgentSlackTagsPage->>useSlackTags: Re-fetch with period="weekly"
    useSlackTags->>API: fetchSlackTags(token, "weekly")
    API-->>useSlackTags: Updated SlackTagsResponse
    useSlackTags-->>CodingAgentSlackTagsPage: New data
    CodingAgentSlackTagsPage->>Browser: Re-render with new period data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hopping through data with SQL and with cheer,
Slack tags now sparkle on dashboards so clear!
Charts dance their dates, tables sort by the time,
Admin analytics... oh what a rhyme!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The pull request title is incomplete and truncated, ending mid-word ('analy'), making it unclear what the main objective is. While it references an admin page creation, the vague Slack user mention and incomplete phrasing fail to clearly communicate the primary change. Complete and clarify the title to accurately describe the main change, such as: 'Add Coding Agent Slack Tags admin page' or similar concise, complete description.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/-u0ajm7x8fbr-admin---docs---ap-1774265132075

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx (1)

35-38: Type casting between identical union types is fragile.

SlackTagsPeriod and PrivyLoginsPeriod are currently identical ("all" | "daily" | "weekly" | "monthly"), but casting between them couples this component to that implementation detail. If either type evolves independently, this will silently break.

Consider one of these approaches:

  1. Reuse PrivyLoginsPeriod directly for the period state if semantically appropriate.
  2. Rename PrivyPeriodSelector to a generic PeriodSelector that accepts a shared period type.
♻️ Option 1: Reuse PrivyLoginsPeriod directly
-import type { SlackTagsPeriod } from "@/types/coding-agent";
-import type { PrivyLoginsPeriod } from "@/types/privy";
+import type { PrivyLoginsPeriod } from "@/types/privy";

 export default function CodingAgentSlackTagsPage() {
-  const [period, setPeriod] = useState<SlackTagsPeriod>("all");
+  const [period, setPeriod] = useState<PrivyLoginsPeriod>("all");
   const { data, isLoading, error } = useSlackTags(period);
   ...
       <PrivyPeriodSelector
-          period={period as PrivyLoginsPeriod}
-          onPeriodChange={(p) => setPeriod(p as SlackTagsPeriod)}
+          period={period}
+          onPeriodChange={setPeriod}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx` around lines 35
- 38, The code is casting between SlackTagsPeriod and PrivyLoginsPeriod which
are currently identical but fragile; update the component to avoid the cast by
either 1) changing the period state/type and setPeriod to use PrivyLoginsPeriod
directly (replace SlackTagsPeriod with PrivyLoginsPeriod where period and
setPeriod are declared) so PrivyPeriodSelector can accept the state without
casting, or 2) make PrivyPeriodSelector generic (rename to PeriodSelector or add
a type param) so it accepts a shared period type and call it with
SlackTagsPeriod explicitly; modify the usage of PrivyPeriodSelector, the period
prop, and the onPeriodChange handler accordingly to remove the explicit cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/coding-agent/page.tsx`:
- Around line 8-10: Page currently renders <CodingAgentSlackTagsPage /> without
checking admin rights; import and call useIsAdmin inside the Page component,
follow the pattern from HomeContent.tsx: const { data: isAdmin, isLoading } =
useIsAdmin(); if (isLoading) return <Skeleton />; if (!isAdmin) return
<AccessDenied />; then only render <CodingAgentSlackTagsPage /> when isAdmin is
true. Ensure you import useIsAdmin, Skeleton, and AccessDenied at the top of the
file and apply these checks inside the exported Page function.

In `@components/CodingAgentSlackTags/SlackTagsColumns.tsx`:
- Around line 15-19: The current replacement of <img> with next/image will fail
for external Slack avatar URLs because next.config.ts lacks
remotePatterns/domains for Slack; update next.config.ts to add a remotePatterns
entry (or domains) that matches Slack avatar hosts (e.g., avatars.slack-edge.com
or other Slack CDN patterns) so next/image accepts those external URLs, then
refactor the SlackTagsColumns component to import Image from 'next/image' and
replace the <img src={tag.user_avatar} alt={tag.user_name} className="..."> with
<Image ... /> using the same src/alt, appropriate width/height or layout/sizes,
and include a fallback for missing avatars (or use unoptimized if you prefer) so
runtime "Invalid src prop" errors are resolved.

In `@hooks/useSlackTags.ts`:
- Around line 15-23: The admin hooks (e.g., useSlackTags) build query keys
without user scoping, causing cached admin data to leak between users; update
each admin hook (useSlackTags, usePulseEmail, usePrivyLogins, useAdminSandboxes,
useAdminSandboxOrgs, useAccountTaskRuns) to include the current user identifier
in the queryKey (for example add user?.id ?? "anon" into the key used in the
queryKey array inside useQuery) OR add cache-clearing on auth transitions by
wiring queryClient.invalidateQueries()/queryClient.clear() to the
authentication/logout lifecycle (ensure PrivyProvider logout callbacks call
this); ensure getAccessToken/ready/authenticated checks still run, and that
queryKey changes or invalidation happen whenever user changes to prevent
cross-user cache reuse.

In `@lib/coding-agent/getTagsByDate.ts`:
- Around line 18-19: getTagsByDate currently slices tag.timestamp directly (see
tag.timestamp and counts[date]) which fails for timezone-aware ISO strings;
convert the timestamp to a UTC date and then derive the YYYY-MM-DD bucket before
incrementing counts. Update getTagsByDate to parse tag.timestamp into a Date (or
use a UTC-aware library), compute the UTC year/month/day, format as "YYYY-MM-DD"
in UTC, and use that string as the key for counts[date] so offsets like "-05:00"
are correctly attributed to the UTC day.

---

Nitpick comments:
In `@components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx`:
- Around line 35-38: The code is casting between SlackTagsPeriod and
PrivyLoginsPeriod which are currently identical but fragile; update the
component to avoid the cast by either 1) changing the period state/type and
setPeriod to use PrivyLoginsPeriod directly (replace SlackTagsPeriod with
PrivyLoginsPeriod where period and setPeriod are declared) so
PrivyPeriodSelector can accept the state without casting, or 2) make
PrivyPeriodSelector generic (rename to PeriodSelector or add a type param) so it
accepts a shared period type and call it with SlackTagsPeriod explicitly; modify
the usage of PrivyPeriodSelector, the period prop, and the onPeriodChange
handler accordingly to remove the explicit cast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14a9fbaa-ba62-4f61-8f46-7c1ef46b16ed

📥 Commits

Reviewing files that changed from the base of the PR and between 986f25c and 0b24941.

📒 Files selected for processing (10)
  • app/coding-agent/page.tsx
  • components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx
  • components/CodingAgentSlackTags/SlackTagsChart.tsx
  • components/CodingAgentSlackTags/SlackTagsColumns.tsx
  • components/CodingAgentSlackTags/SlackTagsTable.tsx
  • components/Home/AdminDashboard.tsx
  • hooks/useSlackTags.ts
  • lib/coding-agent/getTagsByDate.ts
  • lib/recoup/fetchSlackTags.ts
  • types/coding-agent.ts

Comment on lines +8 to +10
export default function Page() {
return <CodingAgentSlackTagsPage />;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate admin-guard patterns used by this route/component stack.
rg -n -C3 'useIsAdmin|isAdmin|admins/check|redirect\(|notFound\(' --type=ts --type=tsx
rg -n -C3 'CodingAgentSlackTagsPage' --type=ts --type=tsx

Repository: recoupable/admin

Length of output: 87


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Fix: remove unsupported file type, search all files with TS/TSX extensions
echo "=== Searching for admin/auth guard patterns ==="
rg -n -C3 'useIsAdmin|isAdmin|admins/check|redirect\(|notFound\(' \
  --glob='*.ts' --glob='*.tsx'

echo ""
echo "=== Searching for CodingAgentSlackTagsPage ==="
rg -n 'CodingAgentSlackTagsPage' --glob='*.ts' --glob='*.tsx'

echo ""
echo "=== Checking the target file ==="
if [ -f "app/coding-agent/page.tsx" ]; then
  cat -n "app/coding-agent/page.tsx"
else
  echo "File not found, searching for similar files..."
  find . -name "*coding-agent*" -type f 2>/dev/null | head -20
fi

Repository: recoupable/admin

Length of output: 3787


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the CodingAgentSlackTagsPage component
echo "=== CodingAgentSlackTagsPage component ==="
cat -n "components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx"

echo ""
echo "=== Check for middleware in app directory ==="
find app -name "middleware*" -type f 2>/dev/null || echo "No middleware files found in app"

echo ""
echo "=== Check for route.ts or layout.tsx in coding-agent ==="
ls -la app/coding-agent/ 2>/dev/null || echo "Directory listing failed"

Repository: recoupable/admin

Length of output: 3657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check for middleware at root level
echo "=== Looking for middleware.ts at repository root ==="
find . -maxdepth 1 -name "middleware.ts" -o -name "middleware.js" 2>/dev/null || echo "No root middleware found"

echo ""
echo "=== Check for app directory layout.tsx ==="
if [ -f "app/layout.tsx" ]; then
  head -50 app/layout.tsx
else
  echo "No app/layout.tsx found"
fi

echo ""
echo "=== Compare with HomeContent pattern (which has proper admin check) ==="
grep -A 15 "const { data: isAdmin" components/Home/HomeContent.tsx

Repository: recoupable/admin

Length of output: 1853


Add admin authorization check to this component.

CodingAgentSlackTagsPage must verify admin status before rendering. Import useIsAdmin and conditionally render an access denied message if the user is not an admin, following the pattern used in HomeContent.tsx:

const { data: isAdmin, isLoading } = useIsAdmin();

if (isLoading) return <Skeleton />;
if (!isAdmin) return <AccessDenied />;

Currently the page renders without any authorization verification, allowing non-admin authenticated users to access admin-only data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/coding-agent/page.tsx` around lines 8 - 10, Page currently renders
<CodingAgentSlackTagsPage /> without checking admin rights; import and call
useIsAdmin inside the Page component, follow the pattern from HomeContent.tsx:
const { data: isAdmin, isLoading } = useIsAdmin(); if (isLoading) return
<Skeleton />; if (!isAdmin) return <AccessDenied />; then only render
<CodingAgentSlackTagsPage /> when isAdmin is true. Ensure you import useIsAdmin,
Skeleton, and AccessDenied at the top of the file and apply these checks inside
the exported Page function.

Comment on lines +15 to +19
<img
src={tag.user_avatar}
alt={tag.user_name}
className="h-6 w-6 rounded-full"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify image configuration for external avatar hosts before switching to next/image.
for f in $(fd 'next.config.*'); do
  echo "== $f =="
  sed -n '1,240p' "$f"
done

Repository: recoupable/admin

Length of output: 184


🏁 Script executed:

cat -n components/CodingAgentSlackTags/SlackTagsColumns.tsx | head -50

Repository: recoupable/admin

Length of output: 1853


🏁 Script executed:

fd '.eslintrc*' -o 'eslint.config.*'

Repository: recoupable/admin

Length of output: 209


🏁 Script executed:

rg 'user_avatar' --type tsx --type ts -B 3 -A 3

Repository: recoupable/admin

Length of output: 87


🏁 Script executed:

fd -t f '\.(eslintrc|eslintrc\.js|eslintrc\.json|eslintrc\.yml|eslintrc\.yaml)$'

Repository: recoupable/admin

Length of output: 42


🏁 Script executed:

fd 'coding-agent' -t f

Repository: recoupable/admin

Length of output: 81


🏁 Script executed:

rg 'interface SlackTag|type SlackTag' -A 5

Repository: recoupable/admin

Length of output: 1618


🏁 Script executed:

cat package.json | head -80

Repository: recoupable/admin

Length of output: 879


🏁 Script executed:

rg '@next/next' -A 2 -B 2

Repository: recoupable/admin

Length of output: 42


🏁 Script executed:

rg 'no-img-element' -r

Repository: recoupable/admin

Length of output: 473


🏁 Script executed:

fd -H '\.eslintrc' -o 'eslint\.config\.'

Repository: recoupable/admin

Length of output: 211


🏁 Script executed:

rg 'no-img-element'

Repository: recoupable/admin

Length of output: 42


🏁 Script executed:

ls -la | grep eslint

Repository: recoupable/admin

Length of output: 126


🏁 Script executed:

cat .eslintrc.json

Repository: recoupable/admin

Length of output: 120


🏁 Script executed:

rg 'remotePatterns|domains' types/ components/ -A 3 -B 3

Repository: recoupable/admin

Length of output: 42


Using next/image requires additional configuration for external Slack avatar URLs.

While the no-img-element lint rule from next/core-web-vitals is active and the refactor direction is sound, the proposed change is incomplete. Switching to next/image without configuring remotePatterns in next.config.ts will cause runtime failures with "Invalid src prop" errors for external Slack avatar URLs.

The refactor must include:

Required next.config.ts changes
-const nextConfig: NextConfig = {};
+const nextConfig: NextConfig = {
+  images: {
+    remotePatterns: [
+      {
+        protocol: 'https',
+        hostname: '*.slack-edge.com',
+      },
+      {
+        protocol: 'https',
+        hostname: '*.slack.com',
+      },
+    ],
+  },
+};

Then apply the component refactor:

Component changes
+import Image from "next/image";
 ...
-          {tag.user_avatar && (
-            <img
-              src={tag.user_avatar}
-              alt={tag.user_name}
-              className="h-6 w-6 rounded-full"
-            />
-          )}
+          {tag.user_avatar && (
+            <Image
+              src={tag.user_avatar}
+              alt={tag.user_name}
+              width={24}
+              height={24}
+              className="h-6 w-6 rounded-full"
+            />
+          )}
🧰 Tools
🪛 GitHub Check: ESLint

[warning] 15-15:
Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CodingAgentSlackTags/SlackTagsColumns.tsx` around lines 15 - 19,
The current replacement of <img> with next/image will fail for external Slack
avatar URLs because next.config.ts lacks remotePatterns/domains for Slack;
update next.config.ts to add a remotePatterns entry (or domains) that matches
Slack avatar hosts (e.g., avatars.slack-edge.com or other Slack CDN patterns) so
next/image accepts those external URLs, then refactor the SlackTagsColumns
component to import Image from 'next/image' and replace the <img
src={tag.user_avatar} alt={tag.user_name} className="..."> with <Image ... />
using the same src/alt, appropriate width/height or layout/sizes, and include a
fallback for missing avatars (or use unoptimized if you prefer) so runtime
"Invalid src prop" errors are resolved.

Comment on lines +15 to +23
return useQuery({
queryKey: ["admin", "coding-agent", "slack-tags", period],
queryFn: async () => {
const token = await getAccessToken();
if (!token) throw new Error("Not authenticated");
return fetchSlackTags(token, period);
},
enabled: ready && authenticated,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether auth transitions clear/invalidate React Query cache.
rg -n -C3 'queryClient\.(clear|removeQueries|resetQueries|invalidateQueries)|logout|onLogout' --type=ts --type=tsx

# Inspect current query keys for admin datasets.
rg -n -C2 'queryKey:\s*\[' --type=ts --type=tsx

Repository: recoupable/admin

Length of output: 87


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for cache invalidation on auth transitions ==="
rg -n -C3 'queryClient\.(clear|removeQueries|resetQueries|invalidateQueries)|logout|onLogout' --type=ts

echo ""
echo "=== Checking usePrivy usage in the codebase ==="
rg -n 'usePrivy' --type=ts -A2

echo ""
echo "=== Examining the specific file: hooks/useSlackTags.ts ==="
fd 'useSlackTags.ts' -type f -exec cat {} \;

echo ""
echo "=== Checking other query keys in admin context ==="
rg -n 'queryKey.*admin' --type=ts -B1 -A1

echo ""
echo "=== Looking for Privy logout handlers ==="
rg -n 'logout' --type=ts -C3

Repository: recoupable/admin

Length of output: 5722


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading useSlackTags.ts ==="
cat hooks/useSlackTags.ts

echo ""
echo "=== Searching for queryClient initialization and setup ==="
rg -n 'queryClient\s*=' --type=ts 

echo ""
echo "=== Searching for Privy provider setup ==="
rg -n 'PrivyProvider\|QueryClientProvider' --type=ts

echo ""
echo "=== Checking root app files for initialization ==="
fd -e ts -e js 'app\.|main\.|_app\.' | head -20

echo ""
echo "=== Checking _app.tsx or similar ==="
fd '_app' -type f | head -5

Repository: recoupable/admin

Length of output: 1083


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading providers/Providers.tsx ==="
cat providers/Providers.tsx

echo ""
echo "=== Checking for logout or auth change handlers ==="
rg -n 'onLogout\|logout\|onAuthStateChange' --type=ts -B2 -A3

echo ""
echo "=== Checking if other admin queries also lack user scope ==="
rg -n 'queryKey.*\["admin' --type=ts -A1

echo ""
echo "=== Looking for Privy docs or integration patterns ==="
rg -n 'PrivyProvider' --type=ts -B2 -A5

Repository: recoupable/admin

Length of output: 659


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading providers/PrivyProvider.tsx ==="
cat providers/PrivyProvider.tsx

echo ""
echo "=== All admin query keys in codebase ==="
rg -n 'queryKey.*\["admin' --type=ts -B1 -A2

echo ""
echo "=== Check if any hook includes user?.id in queryKey ==="
rg -n 'queryKey.*user' --type=ts -B2 -A1

echo ""
echo "=== Looking for useEffect with logout dependency ==="
rg -n 'useEffect.*logout\|logout.*useEffect' --type=ts -B2 -A5

Repository: recoupable/admin

Length of output: 2662


Cache leakage vulnerability across all admin queries—not just useSlackTags.

This is a systemic security issue. All admin hooks (useSlackTags, usePulseEmail, usePrivyLogins, useAdminSandboxes, useAdminSandboxOrgs, useAccountTaskRuns) omit user ID from their query keys. The shared queryClient singleton is never cleared on authentication changes, so cached admin data from one user can be served to another user in the same session.

Required fixes:

  1. Add user scoping to all admin query keys (e.g., ["admin", "coding-agent", "slack-tags", user?.id ?? "anon", period])
  2. OR implement cache invalidation on logout/auth changes via queryClient.invalidateQueries() or queryClient.clear()

The PrivyProvider has no logout callbacks configured, and no cache-clearing logic exists elsewhere in the application.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useSlackTags.ts` around lines 15 - 23, The admin hooks (e.g.,
useSlackTags) build query keys without user scoping, causing cached admin data
to leak between users; update each admin hook (useSlackTags, usePulseEmail,
usePrivyLogins, useAdminSandboxes, useAdminSandboxOrgs, useAccountTaskRuns) to
include the current user identifier in the queryKey (for example add user?.id ??
"anon" into the key used in the queryKey array inside useQuery) OR add
cache-clearing on auth transitions by wiring
queryClient.invalidateQueries()/queryClient.clear() to the authentication/logout
lifecycle (ensure PrivyProvider logout callbacks call this); ensure
getAccessToken/ready/authenticated checks still run, and that queryKey changes
or invalidation happen whenever user changes to prevent cross-user cache reuse.

Comment on lines +18 to +19
const date = tag.timestamp.slice(0, 10); // "YYYY-MM-DD"
counts[date] = (counts[date] ?? 0) + 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify what timestamp formats are documented/produced for slack-tags payloads.
rg -n -C2 'slack-tags|timestamp|coding-agent' --type=ts --type=tsx --type=md --type=json

Repository: recoupable/admin

Length of output: 87


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check file structure and find relevant files
find . -type f -name "*.ts" -o -name "*.js" | grep -E "(getTagsByDate|slack|tag)" | head -20

Repository: recoupable/admin

Length of output: 2133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for timestamp/date-related patterns without problematic file types
rg -n 'slack|timestamp|getTagsByDate' --type=ts --max-count=50

Repository: recoupable/admin

Length of output: 1684


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look at the actual file in question
cat -n lib/coding-agent/getTagsByDate.ts

Repository: recoupable/admin

Length of output: 918


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for where this function is called or how timestamps are generated
rg -n 'getTagsByDate|tag\.timestamp' --type=js --type=ts --max-count=100

Repository: recoupable/admin

Length of output: 473


🏁 Script executed:

#!/bin/bash
cat -n types/coding-agent.ts

Repository: recoupable/admin

Length of output: 532


🏁 Script executed:

#!/bin/bash
cat -n lib/recoup/fetchSlackTags.ts

Repository: recoupable/admin

Length of output: 1309


🏁 Script executed:

#!/bin/bash
rg -n 'timestamp.*:' --type=ts --max-count=50 -A 2 -B 2 | head -100

Repository: recoupable/admin

Length of output: 740


🏁 Script executed:

#!/bin/bash
# Look for test files related to getTagsByDate or SlackTags
find . -path ./node_modules -prune -o -type f \( -name "*.test.ts" -o -name "*.spec.ts" -o -name "__tests__" \) -print | xargs grep -l "getTagsByDate\|SlackTag" 2>/dev/null || echo "No tests found"

Repository: recoupable/admin

Length of output: 74


🏁 Script executed:

#!/bin/bash
# Check for mock data or fixtures
rg -n 'SlackTag\|timestamp' --type=ts -B 2 -A 2 | grep -E "(2026|2025|timestamp.*[0-9]{4})" | head -20

Repository: recoupable/admin

Length of output: 42


UTC bucketing logic is broken for timezone-aware timestamps.

The function's JSDoc (line 9) explicitly declares it should aggregate by UTC date, but the implementation simply slices the timestamp string without normalizing to UTC. For a timestamp like 2026-03-23T23:30:00-05:00 (ISO 8601 with offset), slicing characters 0-9 yields 2026-03-23, when this should count toward 2026-03-24 UTC.

Fix by normalizing to UTC before slicing:

Diff
   for (const tag of tags) {
-    const date = tag.timestamp.slice(0, 10); // "YYYY-MM-DD"
+    const parsed = new Date(tag.timestamp);
+    if (Number.isNaN(parsed.getTime())) continue;
+    const date = parsed.toISOString().slice(0, 10); // UTC "YYYY-MM-DD"
     counts[date] = (counts[date] ?? 0) + 1;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const date = tag.timestamp.slice(0, 10); // "YYYY-MM-DD"
counts[date] = (counts[date] ?? 0) + 1;
for (const tag of tags) {
const parsed = new Date(tag.timestamp);
if (Number.isNaN(parsed.getTime())) continue;
const date = parsed.toISOString().slice(0, 10); // UTC "YYYY-MM-DD"
counts[date] = (counts[date] ?? 0) + 1;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/coding-agent/getTagsByDate.ts` around lines 18 - 19, getTagsByDate
currently slices tag.timestamp directly (see tag.timestamp and counts[date])
which fails for timezone-aware ISO strings; convert the timestamp to a UTC date
and then derive the YYYY-MM-DD bucket before incrementing counts. Update
getTagsByDate to parse tag.timestamp into a Date (or use a UTC-aware library),
compute the UTC year/month/day, format as "YYYY-MM-DD" in UTC, and use that
string as the key for counts[date] so offsets like "-05:00" are correctly
attributed to the UTC day.

Aligns with the simplified endpoint path from the docs PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
channel_name: string;
}

export type SlackTagsPeriod = "all" | "daily" | "weekly" | "monthly";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY - How are these periods any different than periods on other pages?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — implemented in latest commit.

</div>

<div className="mb-6 flex items-center gap-4">
<PrivyPeriodSelector
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor this shared component to be a more generic name please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like

Suggested change
<PrivyPeriodSelector
<PeriodSelector

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — implemented in latest commit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — implemented in latest commit.

- Move route from /coding-agent to /coding (KISS)
- Create shared AdminPeriod type in types/admin.ts (DRY)
- Rename PrivyPeriodSelector to PeriodSelector in components/Admin/ (DRY)
- Extract AdminLineChart shared component (DRY)
- Update PrivyLoginsPage and PrivyLastSeenChart to use shared components

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
components/PrivyLogins/PrivyLastSeenChart.tsx (1)

11-14: Clean refactoring to use shared component.

The delegation to AdminLineChart is well done. One optional performance consideration: getLastSeenByDate(logins) is computed on every render. If the logins array is large, consider memoizing the result.

♻️ Optional: Memoize the data transformation
+"use client";
+
+import { useMemo } from "react";
 import AdminLineChart from "@/components/Admin/AdminLineChart";
 import type { PrivyUser } from "@/types/privy";
 import { getLastSeenByDate } from "@/lib/privy/getLastSeenByDate";

 interface PrivyLastSeenChartProps {
   logins: PrivyUser[];
 }

 export default function PrivyLastSeenChart({ logins }: PrivyLastSeenChartProps) {
+  const data = useMemo(() => getLastSeenByDate(logins), [logins]);
+
   return (
-    <AdminLineChart title="Last Seen Activity" data={getLastSeenByDate(logins)} label="Last Seen" />
+    <AdminLineChart title="Last Seen Activity" data={data} label="Last Seen" />
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/PrivyLogins/PrivyLastSeenChart.tsx` around lines 11 - 14,
PrivyLastSeenChart currently calls getLastSeenByDate(logins) on every render
which can be expensive for large logins arrays; wrap this call in a memo so the
transformed data is recomputed only when logins changes (use React.useMemo
inside the PrivyLastSeenChart component to compute data = useMemo(() =>
getLastSeenByDate(logins), [logins]) and pass that memoized data to
AdminLineChart), referencing PrivyLastSeenChart, getLastSeenByDate, and
AdminLineChart to locate the change.
components/Admin/PeriodSelector.tsx (1)

19-22: Add explicit type="button" for clarity and defensive design.

The buttons on line 19 lack an explicit type attribute. While PeriodSelector is currently used only in non-form contexts, adding type="button" makes intent explicit and prevents accidental form submission if the component is reused in a form context in the future.

Proposed fix
         <button
           key={value}
+          type="button"
           onClick={() => onPeriodChange(value)}
           className={`px-4 py-2 text-sm font-medium transition-colors ${
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Admin/PeriodSelector.tsx` around lines 19 - 22, The button
rendered in the PeriodSelector component lacks an explicit type, which can cause
accidental form submission if reused inside a form; update the button element in
PeriodSelector (the mapped <button key={value} onClick={() =>
onPeriodChange(value)} ...>) to include type="button" to make intent explicit
and defensive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx`:
- Line 30: Update the ApiDocsLink path in CodingAgentSlackTagsPage.tsx: change
the current ApiDocsLink invocation (symbol: ApiDocsLink) to use the correct path
"admins/coding/slack" to match the actual endpoint used by
lib/recoup/fetchSlackTags.ts (which builds requests to
/api/admins/coding/slack); ensure the single ApiDocsLink component invocation in
the CodingAgentSlackTagsPage component is updated so documentation routing
matches the real API route.

---

Nitpick comments:
In `@components/Admin/PeriodSelector.tsx`:
- Around line 19-22: The button rendered in the PeriodSelector component lacks
an explicit type, which can cause accidental form submission if reused inside a
form; update the button element in PeriodSelector (the mapped <button
key={value} onClick={() => onPeriodChange(value)} ...>) to include type="button"
to make intent explicit and defensive.

In `@components/PrivyLogins/PrivyLastSeenChart.tsx`:
- Around line 11-14: PrivyLastSeenChart currently calls
getLastSeenByDate(logins) on every render which can be expensive for large
logins arrays; wrap this call in a memo so the transformed data is recomputed
only when logins changes (use React.useMemo inside the PrivyLastSeenChart
component to compute data = useMemo(() => getLastSeenByDate(logins), [logins])
and pass that memoized data to AdminLineChart), referencing PrivyLastSeenChart,
getLastSeenByDate, and AdminLineChart to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 143c14f9-b898-421d-a50a-2124b9ef805d

📥 Commits

Reviewing files that changed from the base of the PR and between 0b24941 and b59e000.

📒 Files selected for processing (12)
  • app/coding/page.tsx
  • components/Admin/AdminLineChart.tsx
  • components/Admin/PeriodSelector.tsx
  • components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx
  • components/CodingAgentSlackTags/SlackTagsChart.tsx
  • components/Home/AdminDashboard.tsx
  • components/PrivyLogins/PrivyLastSeenChart.tsx
  • components/PrivyLogins/PrivyLoginsPage.tsx
  • lib/recoup/fetchSlackTags.ts
  • types/admin.ts
  • types/coding-agent.ts
  • types/privy.ts
✅ Files skipped from review due to trivial changes (4)
  • components/Home/AdminDashboard.tsx
  • types/admin.ts
  • app/coding/page.tsx
  • types/privy.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/CodingAgentSlackTags/SlackTagsChart.tsx
  • lib/recoup/fetchSlackTags.ts

Slack mentions of the Recoup Coding Agent, pulled directly from the Slack API.
</p>
</div>
<ApiDocsLink path="admins/coding-agent-slack-tags" />
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) ApiDocsLink implementation:"
fd 'ApiDocsLink.tsx' --exec sed -n '1,220p' {}

echo
echo "2) All ApiDocsLink path usages:"
rg -nP --type=tsx -C1 'ApiDocsLink\s+path='

echo
echo "3) Coding Slack-related route/path strings:"
rg -n -C2 'coding-agent-slack-tags|admins/coding/slack|/api/admins/coding/slack|/api/admins/coding'

Repository: recoupable/admin

Length of output: 539


🏁 Script executed:

rg 'ApiDocsLink.*path=' --type-list

Repository: recoupable/admin

Length of output: 449


🏁 Script executed:

rg 'ApiDocsLink.*path=' -A1 -B1

Repository: recoupable/admin

Length of output: 1197


🏁 Script executed:

rg 'coding-agent-slack-tags|coding.*slack|admins/coding' -i

Repository: recoupable/admin

Length of output: 1101


🏁 Script executed:

fd -e tsx -e ts | head -20

Repository: recoupable/admin

Length of output: 817


Update ApiDocsLink path to match the actual API endpoint.

On Line 30, the path admins/coding-agent-slack-tags does not match the actual API endpoint being used. The code in lib/recoup/fetchSlackTags.ts constructs requests to /api/admins/coding/slack, so the ApiDocsLink should use admins/coding/slack instead to route users to the correct documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx` at line 30,
Update the ApiDocsLink path in CodingAgentSlackTagsPage.tsx: change the current
ApiDocsLink invocation (symbol: ApiDocsLink) to use the correct path
"admins/coding/slack" to match the actual endpoint used by
lib/recoup/fetchSlackTags.ts (which builds requests to
/api/admins/coding/slack); ensure the single ApiDocsLink component invocation in
the CodingAgentSlackTagsPage component is updated so documentation routing
matches the real API route.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not applicable — ApiDocsLink path references the Mintlify docs page filename (admins/coding-agent-slack-tags.mdx), not the API endpoint path. The docs page is correctly named coding-agent-slack-tags.mdx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sweetmantech, thanks for the clarification! That makes sense — the ApiDocsLink path corresponds to the Mintlify documentation page filename, not the API endpoint route. My analysis was incorrect. I'll note this for future reviews.


✏️ Learnings added
Learnt from: sweetmantech
Repo: recoupable/admin PR: 20
File: components/CodingAgentSlackTags/CodingAgentSlackTagsPage.tsx:30-30
Timestamp: 2026-03-23T16:51:34.228Z
Learning: In the `recoupable/admin` repository, the `ApiDocsLink` component's `path` prop references the Mintlify docs page filename (e.g., `admins/coding-agent-slack-tags` maps to `admins/coding-agent-slack-tags.mdx`), NOT the actual API endpoint path. Do not flag mismatches between the `ApiDocsLink` path and the API endpoint URL used in fetch calls.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor Author

@sweetmantech sweetmantech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved all review feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this simple wrapper and just directly use AdminLineChart wherever SlackTagsChart is currently used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — removed wrapper, using AdminLineChart directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this simple wrapper and directly use AdminLineChart anywhere PrivyLastSeenChart is currently used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — removed wrapper, using AdminLineChart directly.

Use AdminLineChart directly in both pages instead of thin wrappers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants